/** @file    TaskManagerBundle.cpp
 *  @author  Alessandro Polo
 *  @version $Id: TaskManagerBundle.cpp 2890 2010-08-12 01:57:23Z alex $
 *  @brief
 * File containing methods for the wosh::services::TaskManagerBundle class.
 * The header for this class can be found in TaskManagerBundle.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/

 #include "TaskManagerBundle.h"

 #include <core/ObjectFactory.h>
 #include <core/MethodsCommon.h>
 #include <core/DataModels.h>
 #include <core/BusRing.h>
 #include <core/PersistenceManager.h>
 #include <core/BusManager.h>
 #include <core/FileSystem.h>
 #include <core/SystemError.h>
 #include <core/WoshKernel.h>


using namespace std;
namespace wosh {
 namespace services {

 WOSH_REGISTER(wosh::services::TaskManagerBundle, "wosh::interfaces::services::TaskManager", _TaskManager_VERSION, _static_TaskManagerBundle1 )
 WOSH_REGISTER(wosh::services::TaskManagerBundle, "wosh::BundleGeneric", _TaskManager_VERSION, _static_TaskManagerBundle2 )

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS

TaskManagerBundle::TaskManagerBundle()
	: BundleGeneric() {
	BundleGeneric::setName( _TaskManager_NAME, false );
	Properties.update( _KEY_Version, _TaskManager_VERSION );
	Requirements.add<wosh::xtended::Scheduler>();

	Object::getURI().fromString( _TaskManager_URI );
	Object::getURI().registerGlobal(); // so BundleGeneric won't overwrite my uri

	Log(LOG_DEBUG, " Setting up Scheduler.." );
	this->scheduler = ObjectFactory::createTypeOf<wosh::xtended::Scheduler>();
	if ( this->scheduler != NULL ) {
		this->scheduler->setListener(this);
		this->scheduler->setOwnerObject(this);
	 }
//Log.setLevel(LOG_VERBOSE);

	Log(LOG_DEBUG, " Setting default properties and permissions.." );
	Properties.set( _TaskManager_KEY_DB,	"$DATABASE/automations/", Permission::RW_RW_R__() );

//	Properties.set( "SaveOnExit", true, Permission( Permission::RX, Permission::RX, Permission::Read ) );
//save archived
//save on exit

	Log(LOG_DEBUG, " Registering methods.." );
	MethodMessageResponse* mmExecTask = Methods.create<MethodMessageResponse>( _TaskManager_METHOD_exec, "exec Task", Permission::R_XR_XR__() );
	mmExecTask->setMethod( this, (MethodMessageResponsePtr)&TaskManagerBundle::mmDoExecTask );

	MethodRequest* mmReloadTasks = Methods.create<MethodRequest>( _TaskManager_METHOD_reload, "Reload Tasks", Permission::R_XR__R__() );
	mmReloadTasks->setMethod( this, (MethodRequestPtr)&TaskManagerBundle::mmDoReloadTasks );

	MethodMessageResponse* mmScheduleTask = Methods.create<MethodMessageResponse>( _TaskManager_METHOD_schedule, "schedule Task", Permission(Permission::RX) );
	mmScheduleTask->setMethod( this, (MethodMessageResponsePtr)&TaskManagerBundle::mmDoScheduleTask );

	MethodList* mmListTasks = Methods.create<MethodList>( _METHOD_List, "List Tasks", Permission(Permission::RX) );
	mmListTasks->setDataFieldExtractor( DataFieldExtractor<Automation>::createFor< DataAdapterMapT<int64,Automation> >(this->automations, "Tasks") );

	MethodRetrieve* mmRetrieveTasks = Methods.create<MethodRetrieve>( _METHOD_Retrieve, "Retrieve Task(s)", Permission::R_XR_XR__() );
	mmRetrieveTasks->setItemExtractor( ObjectExtractor<Automation, int64>::createFor< DataAdapterMapT<int64,Automation> >(this->automations, &Automation::getID ) );

	setBundleState(Bundle::STATE_CREATED, false);
 }

TaskManagerBundle::~TaskManagerBundle() {
	Log(LOG_INFO, " Destroying.." );
	if ( isBundleRunning() ) {
		Log(LOG_WARNING, "~TaskManagerBundle() : Destroying while RUNNING! Trying to stop.." );
		bundleStop();
	 }
	Log(LOG_DEBUG, ":~GardenerBundle() : Destroying Scheduler.." );
	WRESULT destroyed = ObjectFactory::destroy(this->scheduler);
	this->scheduler = NULL;
	if ( WFAILED(destroyed) ) {
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "bundleStart", "FAILED CreatingScheduler [%d]", destroyed );
	 }
	Log(LOG_VERBOSE, ":~TaskManagerBundle() : Destroyed." );
 }

//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////// BUNDLE CONTROL

WRESULT TaskManagerBundle::bundleStart() {
	if ( !BundleGeneric::bundleValidate_StartStop(Bundle::STATE_STARTING) ) return WRET_ERR_WRONG_STATE;
	setBundleState( Bundle::STATE_STARTING );

	if ( this->scheduler == NULL ) {
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "bundleStart", "FAILED Creating Scheduler.." );
	 }

	WRESULT bus_ok = WRET_OK;
	if ( !WoshKernel::getInstance()->busses().existsBus(_Bus_Automation) ) {
		Log(LOG_INFO, "bundleStart() : Initializing BusRing ("_Bus_Automation").." );
		bus_ok = WRET_ERR_INTERNAL;
		Bus* automationBus = ObjectFactory::createTypeOf<wosh::BusRing>();
		if ( automationBus == NULL ) {
			SystemError::raise(this, Log, SystemError::CRITICAL, 0, "bundleStart", "FAILED#%d Creating BusRing instance!" );
		 }
		else {
			automationBus->setName(_Bus_Automation);
			automationBus->setParentObject(this);
			automationBus->setSharedLocal(true);
			automationBus->setSharedNetwork(true);
			bus_ok = WoshKernel::getInstance()->busses().registerBus(automationBus);
			if ( WFAILED(bus_ok) )
				SystemError::raise(this, Log, SystemError::CRITICAL, 0, "bundleStart", "FAILED#%d Creating Bus ("_Bus_Automation")..", bus_ok );
			else {
				Log(LOG_INFO, "bundleStart() : Created BusRing ("_Bus_Automation")." );
			 }
		 }
	 }
	else {
		Log(LOG_VERBOSE, "bundleStart() : "_Bus_Automation" exists already." );
	 }
	BusAutomation.setMessageHandler(this);
	bus_ok = BusAutomation.connect( _Bus_Automation, Bus::ACTION_DEFERRED );
	if ( WFAILED(bus_ok) )
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "bundleStart", "FAILED#%d Connecting Bus ("_Bus_Automation")..", bus_ok );
	else
		Log(LOG_DEBUG, " Connected to "_Bus_Automation"." );

	WRESULT ret = WRET_OK;
	ret += this->scheduler->setThreadPoolCount(3);
	ret += this->scheduler->startScheduler(30000);

	loadAutomations();

	if ( WSUCCEEDED(ret) )
		setBundleState( Bundle::STATE_STARTED );
	return ret;
 }

WRESULT TaskManagerBundle::bundleStop() {
	if ( !BundleGeneric::bundleValidate_StartStop(Bundle::STATE_STOPPING) ) return WRET_ERR_WRONG_STATE;
	setBundleState( Bundle::STATE_STOPPING );

	saveAutomations();

	WRESULT ret = this->scheduler->stopScheduler(30000);

	if ( WSUCCEEDED(ret) )
		setBundleState( Bundle::STATE_STOPPED );
	return ret;
 }

////////////////////////////////////////////////////////////////////////////////////////////// BUNDLE CONTROL
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void TaskManagerBundle::busMessage( const Message& message, const Bus* source ) {
	if ( message.isEmpty() ) return;
	if ( !MessageFilter::filterReplica(message, &BusCore) ) return;
	BundleGeneric::busMessage(message, source);
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////// PROPERTY EVENT

bool TaskManagerBundle::updatingProperty( bool& do_update, const Variant& value_proposed, Property& property_current, const PropertiesProvider* source ) {
	return BundleGeneric::updatingProperty(do_update, value_proposed, property_current, source);
 }

////////////////////////////////////////////////////////////////////////////////////////////// PROPERTY EVENT
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT TaskManagerBundle::addAutomation( Automation* automation ) {
	this->automations.transactionBeginWrite();
	WRESULT ret = addAutomation_(automation);
	this->automations.transactionEnd();
	return ret;
 }

WRESULT TaskManagerBundle::addAutomation_( Automation* automation ) {
	if ( automation == NULL ) return WRET_ERR_PARAM;
	automation->getURI().setKernelLocal();
	automation->getURI().setParent( this->getURI() );
	automation->getURI().setName( automation->getName() );
	automation->getURI().registerGlobal();
	automation->init();
	Log(LOG_INFO, ":addAutomation(%s) Added", automation->getName().c_str() );
	this->automations.set(automation->getID(), automation);
	return WRET_OK;
 }

WRESULT TaskManagerBundle::execAutomation_( Automation* automation ) {
	if ( automation == NULL ) return WRET_ERR_PARAM;
	automation->execute(NULL);
	//Message* message = new Message(new Request(_Automation_METHOD_execute));
	//message->setDestination( automation->getURI() );
	//signMessage(message);
	//BusAutomation.postMessage(message);
	return WRET_OK;
 }

void TaskManagerBundle::taskEvent( wosh::xtended::SchedulerTask* task, int64, wosh::xtended::Scheduler* ) {
	if ( task == NULL ) return;
	if ( task->getID() == 0 ) {
		Log(LOG_WARNING, ":taskEvent() Task ID is 0. Removing Task" );
		return;
	 }
	this->automations.transactionBeginRead();
	Automation* automation = this->automations.find(task->getID());
	if ( automation == NULL ) {
		this->automations.transactionEnd();
		Log(LOG_CRITICAL, ":taskEvent(%"PRId64") : NO linked-Automation found. Removing Task", task->getID() );
		return;
	 }
	execAutomation_(automation);
	this->automations.transactionEnd();
	return;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT TaskManagerBundle::loadAutomations() {
	FilePath xmlFile;
	if ( !xmlFile.set( Properties.getValueOf(_TaskManager_KEY_DB).toString() ) ) {
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "loadAutomations", "FAILED#1 Invalid Archive File [%s]", xmlFile.getPath().c_str() );
		return WRET_ERR_PARAM;
	 }
	if ( !xmlFile.exists() ) {
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "loadAutomations", "FAILED#1 Archive File doesn't exists %s [%s]!", xmlFile.getPath().c_str(), xmlFile.getPathLocal().c_str() );
		return WRET_ERR_PARAM;
	 }
	// may also be a folder of .xml files
	Log(LOG_VERBOSE, ":loadAutomations() File %s ..", xmlFile.getPathLocal().c_str() );

	std::vector<Automation*> objects;
	WRESULT ret = PersistenceManager::loadObjects( objects, "XML", xmlFile.getPathLocal() );

	if ( WFAILED(ret) ) {
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "loadAutomations", "FAILED#%d loading Database %s..", ret, xmlFile.getPathLocal().c_str() );
		return ret;
	 }
	if ( objects.size() == 0 ) {
		Log(LOG_WARNING, ":loadAutomations() : No Items found in database.." );
		return WRET_OK;
	 }

	// register objects
	this->automations.transactionBeginWrite();
	std::vector<Automation*>::iterator it;
	for ( it=objects.begin(); it!=objects.end(); it++ ) {
		if ( *it == NULL ) continue;
		addAutomation_( *it );
	 }
	this->automations.transactionEnd();

	return ret;
 }

WRESULT TaskManagerBundle::saveAutomations() {
	FilePath xmlFile;
	xmlFile.set("$DATABASE/automations/automations2.bak");
	Log(LOG_VERBOSE, ":saveAutomations() File %s ..", xmlFile.getPathLocal().c_str() );
	this->automations.transactionBeginRead();
	WRESULT ret = PersistenceManager::saveObjects( this->automations.getMap(), "XML", xmlFile.getPathLocal() );
	this->automations.transactionEnd();
	if ( WFAILED(ret) ) {
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "saveAutomations", "FAILED#%d saving Database %s..", ret, xmlFile.getPathLocal().c_str() );
	 }
	return ret;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////// METHODS

Response* TaskManagerBundle::mmDoExecTask( const Message* messageRequest ) {
	if ( messageRequest == NULL || messageRequest->isEmpty() ) return NULL;
	const Request* request = messageRequest->getContent()->asRequest();
	if ( request == NULL ) return NULL;
	WRESULT ret = WRET_OK;
	Variant taskID;
	request->extractArguments(taskID);
	if ( taskID.isNumeric() ) {
		int64 id = taskID.toInt64(0);
		if ( id == 0 )
			return request->replyResponse(WRET_ERR_PARAM, "Invalid id (0) [int64]");
		Log(LOG_VERBOSE, ":mmDoListTasks(%"PRId64") (by ID)", id );
		this->automations.transactionBeginRead();
		Automation* automation = this->automations.find(id);
		if ( automation == NULL ) {
			this->automations.transactionEnd();
			return request->replyResponse(WRET_ERR_PARAM, "Automation not found [int64]");
		 }
		ret = execAutomation_(automation);
		this->automations.transactionEnd();
	 }
	else if ( taskID.isStringNotEmpty() ) {
		this->automations.transactionBeginRead();
		Log(LOG_VERBOSE, ":mmDoListTasks(%s) (by Name)", taskID.asString().c_str() );
		tInt64AutomationMap::ConstIterator it;
		const tInt64AutomationMap::ConstIterator it_end = this->automations.end();
		for ( it=this->automations.begin(); it!=it_end; ++it ) {
			if ( it->second == NULL ) continue;
			if ( it->second->getName() != taskID.asString() ) continue;
			ret = execAutomation_(it->second);
			break;
		 }
		this->automations.transactionEnd();
	 }
	else
		return request->replyResponse(WRET_ERR_PARAM, "Invalid parameters: id[int64] | name[str]");
	return request->replyResponse(ret);
 }

Response* TaskManagerBundle::mmDoScheduleTask( const Message* messageRequest ) {
	if ( messageRequest == NULL || messageRequest->isEmpty() ) return NULL;
	const Request* request = messageRequest->getContent()->asRequest();
	if ( request == NULL ) return NULL;
	Variant taskID, nextInterrupt;
	request->extractArguments(taskID, nextInterrupt);
	if ( !taskID.isInt64() || !nextInterrupt.isInt64() )
		return request->replyResponse(WRET_ERR_PARAM, "Invalid Param [int64, int64]");
	Log(LOG_VERBOSE, ":mmScheduleTask(%"PRId64",%"PRId64")", taskID.asInt64(), nextInterrupt.asInt64() );
	WRESULT ret = this->scheduler->setupTask(taskID.asInt64(), nextInterrupt.asInt64());
	return request->replyResponse(ret);
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Response* TaskManagerBundle::mmDoReloadTasks( const Request* request ) {
	if ( request == NULL ) return NULL;
	std::string option = request->getArgument().toString();
	Log(LOG_VERBOSE, ":mmDoReloadBObj(%s)", option.c_str() );
	this->automations.free();
	WRESULT ret = loadAutomations();
 	return request->replyResponse(ret);
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

 }; // namespace services
}; // namespace wosh
